// Copyright (c) 2018, the Dart project authors.  Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

import 'dart:io';
import 'dart:typed_data';

// ignore: implementation_imports
import 'package:front_end/src/api_unstable/dart2js.dart'
    as ir
    show serializeComponent, ByteSink;
import 'package:kernel/ast.dart' as ir;
import 'package:kernel/binary/ast_from_binary.dart' show BinaryBuilder;

import '../diagnostics/diagnostic_listener.dart';
import '../environment.dart';
import '../inferrer/abstract_value_strategy.dart';
import '../inferrer/types.dart';
import '../js_model/js_world.dart';
import '../options.dart';
import '../source_file_provider.dart';
import 'serialization.dart';
import 'task.dart';

abstract class SerializationStrategy<T> {
  const SerializationStrategy();

  Uint8List unpackAndSerializeComponent(GlobalTypeInferenceResults results) {
    JClosedWorld closedWorld = results.closedWorld;
    ir.Component component = closedWorld.elementMap.programEnv.mainComponent;
    return serializeComponent(component);
  }

  List<T> serializeGlobalTypeInferenceResults(
    GlobalTypeInferenceResults results,
    CompilerOptions options,
    SerializationIndices indices,
  );

  Uint8List serializeComponent(ir.Component component) {
    return ir.serializeComponent(component);
  }

  ir.Component deserializeComponent(Uint8List data) {
    ir.Component component = ir.Component();
    BinaryBuilder(data).readComponent(component);
    return component;
  }

  GlobalTypeInferenceResults deserializeGlobalTypeInferenceResults(
    CompilerOptions options,
    DiagnosticReporter reporter,
    Environment environment,
    AbstractValueStrategy abstractValueStrategy,
    ir.Component component,
    JClosedWorld closedWorld,
    List<T> globalTypeInferenceResultsData,
    SerializationIndices indices,
  );

  List<T> serializeClosedWorld(
    JClosedWorld closedWorld,
    CompilerOptions options,
    SerializationIndices indices,
  );

  JClosedWorld deserializeClosedWorld(
    CompilerOptions options,
    DiagnosticReporter reporter,
    AbstractValueStrategy abstractValueStrategy,
    ir.Component component,
    List<T> data,
    SerializationIndices indices,
  );
}

class BytesInMemorySerializationStrategy extends SerializationStrategy<int> {
  final bool useDataKinds;

  const BytesInMemorySerializationStrategy({this.useDataKinds = false});

  @override
  List<int> serializeGlobalTypeInferenceResults(
    GlobalTypeInferenceResults results,
    CompilerOptions options,
    SerializationIndices indices,
  ) {
    ir.ByteSink byteSink = ir.ByteSink();
    DataSinkWriter sink = DataSinkWriter(
      BinaryDataSink(byteSink),
      options,
      indices,
      useDataKinds: useDataKinds,
    );
    serializeGlobalTypeInferenceResultsToSink(results, sink);
    return byteSink.builder.takeBytes();
  }

  @override
  GlobalTypeInferenceResults deserializeGlobalTypeInferenceResults(
    CompilerOptions options,
    DiagnosticReporter reporter,
    Environment environment,
    AbstractValueStrategy abstractValueStrategy,
    ir.Component component,
    JClosedWorld closedWorld,
    List<int> globalTypeInferenceResultsData,
    SerializationIndices indices,
  ) {
    DataSourceReader globalTypeInferenceResultsSource = DataSourceReader(
      BinaryDataSource(globalTypeInferenceResultsData),
      options,
      indices,
      useDataKinds: useDataKinds,
    );
    final results = deserializeGlobalTypeInferenceResultsFromSource(
      options,
      reporter,
      environment,
      abstractValueStrategy,
      component,
      closedWorld,
      globalTypeInferenceResultsSource,
    );
    return results;
  }

  @override
  List<int> serializeClosedWorld(
    JClosedWorld closedWorld,
    CompilerOptions options,
    SerializationIndices indices,
  ) {
    ir.ByteSink byteSink = ir.ByteSink();
    DataSinkWriter sink = DataSinkWriter(
      BinaryDataSink(byteSink),
      options,
      indices,
      useDataKinds: useDataKinds,
    );
    serializeClosedWorldToSink(closedWorld, sink);
    return byteSink.builder.takeBytes();
  }

  @override
  JClosedWorld deserializeClosedWorld(
    CompilerOptions options,
    DiagnosticReporter reporter,
    AbstractValueStrategy abstractValueStrategy,
    ir.Component component,
    List<int> data,
    SerializationIndices indices,
  ) {
    DataSourceReader source = DataSourceReader(
      BinaryDataSource(data),
      options,
      indices,
      useDataKinds: useDataKinds,
    );
    var closedWorld = deserializeClosedWorldFromSource(
      options,
      reporter,
      abstractValueStrategy,
      component,
      source,
    );
    return closedWorld;
  }
}

class BytesOnDiskSerializationStrategy extends SerializationStrategy<int> {
  final bool useDataKinds;

  const BytesOnDiskSerializationStrategy({this.useDataKinds = false});

  @override
  List<int> serializeGlobalTypeInferenceResults(
    GlobalTypeInferenceResults results,
    CompilerOptions options,
    SerializationIndices indices,
  ) {
    Uri uri = Uri.base.resolve('world.data');
    DataSinkWriter sink = DataSinkWriter(
      BinaryDataSink(RandomAccessBinaryOutputSink(uri)),
      options,
      indices,
      useDataKinds: useDataKinds,
    );
    serializeGlobalTypeInferenceResultsToSink(results, sink);
    return File.fromUri(uri).readAsBytesSync();
  }

  @override
  GlobalTypeInferenceResults deserializeGlobalTypeInferenceResults(
    CompilerOptions options,
    DiagnosticReporter reporter,
    Environment environment,
    AbstractValueStrategy abstractValueStrategy,
    ir.Component component,
    JClosedWorld closedWorld,
    List<int> globalTypeInferenceResultsData,
    SerializationIndices indices,
  ) {
    DataSourceReader globalTypeInferenceResultsSource = DataSourceReader(
      BinaryDataSource(globalTypeInferenceResultsData),
      options,
      indices,
      useDataKinds: useDataKinds,
    );
    return deserializeGlobalTypeInferenceResultsFromSource(
      options,
      reporter,
      environment,
      abstractValueStrategy,
      component,
      closedWorld,
      globalTypeInferenceResultsSource,
    );
  }

  @override
  List<int> serializeClosedWorld(
    JClosedWorld closedWorld,
    CompilerOptions options,
    SerializationIndices indices,
  ) {
    Uri uri = Uri.base.resolve('closed_world.data');
    DataSinkWriter sink = DataSinkWriter(
      BinaryDataSink(RandomAccessBinaryOutputSink(uri)),
      options,
      indices,
      useDataKinds: useDataKinds,
    );
    serializeClosedWorldToSink(closedWorld, sink);
    return File.fromUri(uri).readAsBytesSync();
  }

  @override
  JClosedWorld deserializeClosedWorld(
    CompilerOptions options,
    DiagnosticReporter reporter,
    AbstractValueStrategy abstractValueStrategy,
    ir.Component component,
    List<int> data,
    SerializationIndices indices,
  ) {
    DataSourceReader source = DataSourceReader(
      BinaryDataSource(data),
      options,
      indices,
      useDataKinds: useDataKinds,
    );
    var closedWorld = deserializeClosedWorldFromSource(
      options,
      reporter,
      abstractValueStrategy,
      component,
      source,
    );
    return closedWorld;
  }
}

class ObjectsInMemorySerializationStrategy
    extends SerializationStrategy<Object> {
  final bool useDataKinds;

  const ObjectsInMemorySerializationStrategy({this.useDataKinds = true});

  @override
  List<Object> serializeGlobalTypeInferenceResults(
    GlobalTypeInferenceResults results,
    CompilerOptions options,
    SerializationIndices indices,
  ) {
    List<Object> data = [];
    DataSinkWriter sink = DataSinkWriter(
      ObjectDataSink(data),
      options,
      indices,
      useDataKinds: useDataKinds,
    );
    serializeGlobalTypeInferenceResultsToSink(results, sink);
    return data;
  }

  @override
  GlobalTypeInferenceResults deserializeGlobalTypeInferenceResults(
    CompilerOptions options,
    DiagnosticReporter reporter,
    Environment environment,
    AbstractValueStrategy abstractValueStrategy,
    ir.Component component,
    JClosedWorld closedWorld,
    List<Object> globalTypeInferenceResultsData,
    SerializationIndices indices,
  ) {
    DataSourceReader globalTypeInferenceResultsSource = DataSourceReader(
      ObjectDataSource(globalTypeInferenceResultsData),
      options,
      indices,
      useDataKinds: useDataKinds,
    );
    return deserializeGlobalTypeInferenceResultsFromSource(
      options,
      reporter,
      environment,
      abstractValueStrategy,
      component,
      closedWorld,
      globalTypeInferenceResultsSource,
    );
  }

  @override
  List<Object> serializeClosedWorld(
    JClosedWorld closedWorld,
    CompilerOptions options,
    SerializationIndices indices,
  ) {
    List<Object> data = [];
    DataSinkWriter sink = DataSinkWriter(
      ObjectDataSink(data),
      options,
      indices,
      useDataKinds: useDataKinds,
    );
    serializeClosedWorldToSink(closedWorld, sink);
    return data;
  }

  @override
  JClosedWorld deserializeClosedWorld(
    CompilerOptions options,
    DiagnosticReporter reporter,
    AbstractValueStrategy abstractValueStrategy,
    ir.Component component,
    List<Object> data,
    SerializationIndices indices,
  ) {
    DataSourceReader source = DataSourceReader(
      ObjectDataSource(data),
      options,
      indices,
      useDataKinds: useDataKinds,
    );
    var closedWorld = deserializeClosedWorldFromSource(
      options,
      reporter,
      abstractValueStrategy,
      component,
      source,
    );
    return closedWorld;
  }
}
